package com.example.shop.rabbitmq.comsumer;

import com.example.shop.es.service.ElasticSearchClient;
import com.example.shop.goods.dto.GoodsDetailBrowseInfo;
import com.example.shop.mapper.GoodsClassMapper;
import com.example.shop.rabbitmq.component.GoodsComponent;
import com.example.shop.rabbitmq.dto.InventoryDetect;
import com.example.shop.redis.enums.RedisKey;
import com.example.shop.redis.service.LettuceClient;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Component
public class GoodsConsumer {

    private final GoodsClassMapper goodsClassMapper;
    private final LettuceClient redisClient;
    private final ElasticSearchClient esClient;
    private final RabbitTemplate rabbitTemplate;

    public GoodsConsumer(GoodsClassMapper goodsClassMapper, LettuceClient redisClient, ElasticSearchClient esClient, RabbitTemplate rabbitTemplate) {
        this.goodsClassMapper = goodsClassMapper;
        this.redisClient = redisClient;
        this.esClient = esClient;
        this.rabbitTemplate = rabbitTemplate;
    }


    /**
     * 检测redis中的库存，采用太久未使用策略，进行数据同步更新
     * 每隔一段时间，检测redis数据库中商品对应库存map里的库存
     * 如果库存map不存在，则删除该消息，即消费消息
     * 如果在规定时间内有使用 或者 该款式被修改导致mq中的消息不准确，则重新发布消息(最新的款式信息)入队，等待下一次检测
     * 如果库存map太久未使用，则删除redis中该库存map，并将最新的库存信息更新到es和数据库中
     * @param inventoryDetect 旧库存map信息
     * @param message mq消息消息
     * @param channel 通道
     */
    @RabbitListener(queues = {GoodsComponent.INVENTORY_DETECT_QUEUE},ackMode = "MANUAL")
    public void inventoryDetectConsumer(InventoryDetect inventoryDetect, Message message, Channel channel) throws IOException {
        Long goodsId = inventoryDetect.getGoodsId();
        Map<String, Integer> inventoryMap = inventoryDetect.getInventoryMap();
        //检测redis中的该商品的款式最新信息，是否需要写入到数据库和es中
        Map<Object, Object> newestInventoryMap = redisClient.hGetMap(RedisKey.HashMap.GOODS_INVENTORY.getKey() + goodsId);
        //redis中是否存在该键对应的库存map
        if(newestInventoryMap.isEmpty()){
            //redis中获取不到该map，将删除该消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            //不进行处理
            return;
        }
        //对该商品的所有款式进行检测是否使用过
        Set<Map.Entry<Object, Object>> newestSet = newestInventoryMap.entrySet();
        for(Map.Entry<Object, Object> newestEntry:newestSet){
            //获取旧的库存map中键名为最新库存信息的该款式名的库存信息
            Integer inventory = inventoryMap.get(newestEntry.getKey());
            //如果库存为null，说明该款式被修改过,即旧的库存map中没有该款式，应该重新发送该消息为最新的
            //或者 判断库存是否变化过，如果变化过则代表被使用过，即重新发布最新消息
            if(inventory==null || newestEntry.getValue() != inventory){
                //发布最新库存map的消息
                rabbitTemplate.convertAndSend(GoodsComponent.GOODS_EXCHANGE,GoodsComponent.INVENTORY_DETECT_ROUTING_KEY,
                        new InventoryDetect(goodsId, (Map<String, Integer>) newestEntry));
                //消费掉旧的消息
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
                return;
            }
        }
        //否则代表没有使用过，则删除redis中该库存map，并将最新的库存信息更新到es和数据库中
        //设置分布式键，保证进行数据写入到mysql和es时，不能更改redis中的对应的库存数据
        redisClient.set(RedisKey.SYNC_ME_INVENTORY.getKey() + goodsId,true);

        //写入到数据库中
        goodsClassMapper.updateInventory(goodsId,inventoryMap);
        //写入到es中
        String script =
                "if(ctx._source['goodsStyleList'] != null){" +
                    "for(int i=0 ; i<ctx._source['goodsStyleList'].length ;i++){" +
                        "def styleList = ctx._source['goodsStyleList'];" +
                        "def inventoryMap = params.inventoryMap;" +
                        "def newestInventory = inventoryMap.get(styleList[i].goodsClass);" +
                        "if( newestInventory != null ){" +
                            "styleList[i].classInventory = newestInventory;" +
                        "}" +
                    "}" +
                "}";
        HashMap<String, Object> params = new HashMap<>();
        params.put("inventoryMap",inventoryMap);
        GoodsDetailBrowseInfo detailBrowseInfo = new GoodsDetailBrowseInfo(goodsId);
        esClient.updateDocument(detailBrowseInfo,script,params);


        //删除库存map，因为库存map太久未使用了
        //删除分布式键，代表可以对redis中的数据进行操作了
        String scriptStr =
                "redis.call('DEL',KEYS[1]);" +
                "redis.call('DEL',KEYS[2]);" +
                "return true;";
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(scriptStr,Boolean.class);
        String key1 = RedisKey.HashMap.GOODS_INVENTORY.getKey() + goodsId;
        String key2 = RedisKey.SYNC_ME_INVENTORY.getKey() + goodsId;
        redisClient.execLua(redisScript, Arrays.asList(key1,key2));

    }
}
